// es6 --   router
// 作者 王欢
// 开始日期 2019-12-20
// 解决小程序  路由跳转  拦截  重定向问题  
// 基本思路与用法  参考vue
const { $copy, $toClass, $trimAll } = require("./common");
const { interceptRoutes } = require("./config");
const querystring = require("../assets/plugs/qs");
const path = require("../assets/plugs/path");

/* 路由拦截
第一个参数为 即将进入的路由
当beforeEnter 返回为一个  resolve状态promise对象 则执行路由跳转
仅适用走内置扩展方法 $url
    
    to from 参数都有 path , options参数
    from 多添一个  mathod 跳转方法参数
    to  or from commonParams = >{
        path:""
        ,options:""
    }
    from privateParams = >{
        method:""
    }
    默认有拦截函数   不执行下面的跳转
    可以调用next执行  to的正常跳转  参数无效
    替他跳转  请使用原生方法 
    next 函数可以传参  参数用法 同 $url arguments[1] 一致	以便于重定向
*/
let _beforeEnter = null;
if ( interceptRoutes && interceptRoutes.routes ) {
    _beforeEnter = function (to, from, next) {
        const {
            path
        } = to;
        const router_beforeEnter = interceptRoutes.beforeEnter;
        const { routes } = interceptRoutes;
        let hasIntercept = false;
        if (routes && routes.length) {
            /* 执行路由拦截函数 */
            function intercept_route(route) {
                const route_beforeEnter = route.beforeEnter;
                if (route_beforeEnter && typeof route_beforeEnter === "function") {
                    route_beforeEnter({ to, from, next });
                } else {
                    next();
                }
            }

            for (let i = 0, len = routes.length; i < len; i++) {
                const route = routes[i];
                const route_path = routes[i].path;
                const route_path_type = $toClass(route_path);
                /* 如果匹配路由 */
                if (
                    (route_path_type === "String" && path === route_path)
                    || (route_path_type === "RegExp" && route_path.test(path))
                    || (route_path_type === "Array" && route_path.includes(path))
                ) {
                    /* 执行全局路由拦截函数 */
                    if (router_beforeEnter && typeof router_beforeEnter === "function") {

                        router_beforeEnter({ to, from, next })
                            .then(() => {
                                intercept_route(route);
                            }).catch(() => {
                                console.log("global router intercept off");
                            });
                    } else {
                        /* 执行路由拦截函数 */
                        intercept_route(route);
                    }
                    hasIntercept = true;
                    break;
                }

            }
        }

        if (!hasIntercept) {
            next();
        }
    }
}

// 路由跳转
// 使用方法  绑定DOM
// 强调 --- method 可以使用官方 api 如'switchTab' 'navigateTo'
// 也可以使用简写 api 
/* <view 
       bindtap="$url" 
       data-url="/pages/index/index" 
       data-options="name=xx&age=xx" 
       data-method="{{ 'url' || 'replace' || 'close' || 'switch'  }}" 
   ></view> */
// 主动调用
/* $url(undefined,{
    url:"/pages/index/index",
    options: "name=xx&age=xx" || { name:"xx",age:"xx" },
    method:"url" || "replace" || "close" || "switch" 
}); */
/* 
    $url(event:EventObject[,opts:any]);
    $url(undefined:void,opts:Object);
*/
function $url(event, opts) {

    //默认参数
    // let options = {
    //     url, // String
    //     options, // String || Object
    //     method, // String
    // };
    // 根据 event 判断 绑定DOM 还是 主动调用
    // 根据 event 判断 绑定DOM 还是 主动调用
    let AssOpts;
    if (event) {
        AssOpts = event.currentTarget.dataset;
    } else {
        // 主动调用 进行类型判断
        if (opts) {
            let optsType = $toClass(opts);
            // 如果是字符串 进行简单分割
            if (optsType == "String") {
                let [url, options] = opts.split("?");
                AssOpts = {
                    url,
                    options
                };
                // 如果是对象格式 直接赋值拷贝    
            } else if (optsType == "Object") {
                AssOpts = opts;
            } else {
                throw new TypeError('the opts is must be String or Object');
            }
        } else {
            throw new ReferenceError("the opts is not defined");
        }
    }

    // 把参数合并 同时获取 url
    const { url } = AssOpts;
    // 判断 url是否存在 类型是否正确
    if (url && typeof url == "string") {
        // 判断 options 是否 存在
        // 主要进行  options对象类型  转换为 序列化的字符串
        let sezOpts = "";
        let { options } = AssOpts;
        let toOptsOrigin = options;
        if (options) {
            //严格类型判断
            let opType = $toClass(options);
            // 对象类型进行字符串序列化
            if (opType == 'Object') {
                sezOpts = querystring.stringify(options, {
                    encode: false
                });
            } else if (opType == 'String') {
                sezOpts = $trimAll(options);
            } else {
                throw new TypeError('the options must be Object or String');
            }
        }
        if (sezOpts) {
            sezOpts = "?" + sezOpts;
            // 删除 params.options 避免与未来 api参数冲突
            delete AssOpts.options;
        }
        // 根据 options 判断跳转类型
        // 主要微信api文档提供的方法实在单词是很难记住 用简单单词代替 也比较符合api的功能 
        // 默认 navigateTo 与 文档一致  同时存在的情况下   全职如下由大变小
        // ( params == false || url) == navigateTo
        // switch == switchTab
        // close  ==  reLaunch
        // replace == redirectTo
        let { method } = AssOpts;
        let urlMethod;
        if (method) {
            switch (method) {
                case "redirectTo":
                case "replace":
                    urlMethod = "redirectTo";
                    break;
                case "switch":
                case "switchTab":
                    urlMethod = "switchTab";
                    break;
                case "close":
                case "reLaunch":
                    urlMethod = "reLaunch";
                    break;
                case "url":
                case "navigateTo":
                    urlMethod = "navigateTo";
                    break;
                default:
                    throw new Error("the method is not a right interface");
            }
            // 删除 params.method 避免与未来 api参数冲突
            delete AssOpts.method;
        } else {
            urlMethod = "navigateTo";
        }

        //改变url为dd api格式  主要可以执行额外的回调函数
        AssOpts.url = url + sezOpts;
        function nextEnter(nextParams) {
            if (nextParams === undefined) {
                dd[urlMethod](AssOpts);
            } else {
                $url(undefined, nextParams);
            }
        }
        if (_beforeEnter && typeof _beforeEnter === "function") {
            const pagesArr = getCurrentPages();
            const curPage = pagesArr[pagesArr.length - 1];
            let transToOpts = toOptsOrigin || {};
            if (toOptsOrigin
                && typeof toOptsOrigin === "string"
            ) {
                transToOpts = querystring.parse(toOptsOrigin);
            }
            const { route, options } = curPage;
            const fromPath = "/" + route;
            let toPath = url;

            const toRouteAbsAuto = path.isAbsolute(url);
            if (!toRouteAbsAuto) {
                const dirList = route.split("/");
                dirList.pop();
                const dirPath = dirList.join("/");
                toPath = "/" + path.join(dirPath, url);
            }
            const toPage = {
                path: toPath
                , options: transToOpts
            };
            const fromPage = {
                path: fromPath
                , options: options
                , method: urlMethod
                , page: curPage
            };
            _beforeEnter(toPage, fromPage, nextEnter);
        } else {
            nextEnter();
        }
    } else {
        throw new ReferenceError("params url is must be required");
    }
}
// 路由 -- 返回
// 主要用于 在当前页面异步改变其他页面内容 
// 注意  
// 此方法基于 dd.navigateBack getCurrentPages
// 请确保需要操作页面存在于路由栈中(getCurrentPages())
// 使用方法  绑定DOM
// 强调 --- method 可以使用官方 api 如'switchTab' 'navigateTo'
// 也可以使用简写 api 
/* <view 
       bindtap="$back" 
       url="/pages/index/index" || level="1"
       options="name=xx&age=xx" 
   ></view> */
// 主动调用
/* $back(undefined,{
    url:"/pages/index/index" || leve: 1,
    options: "name=xx&age=xx" || { name:"xx",age:"xx" },
    prevLoad(backPage){}  
}); */
/* 
    $back(event:EventObject[,opts:any]);
    $back(event:void,opts:Object);
*/
function $back(event, opts) {
    const pages = getCurrentPages();
    const pagesLen = pages.length;
    let defaultOpts = {
        //      leve 与 url 同时存在情况下  leve优先
        //      leve,  返回的路由层级 Number || String
        //      url,  返回的路由名称 String
        //      如果 options存在 replace 将为true
        //      options,  传给返回路由参数  此参数将在 返回路由 onLoad中可以获取 String || Object
        //      replace,  如果找到并且成功返回  将会执行一次返回路由 onLoad生命周期函数
        near: true, // 是否就近查找  
        //      prevLoad,  成功回调函数
        // 比如查找路由 - 2
        // [1,2,3,2] 默认将按正序查找返回路由 成功返回就是 [2,3,2]
        // 如果是false  将按反序查找  成功返回就是 [2]  
    };
    let AssOpts;
    if (event) {
        AssOpts = $copy(defaultOpts, event.currentTarget.dataset);
    } else {
        if (opts && $toClass(opts) == "Object") {
            AssOpts = $copy(defaultOpts, opts);
        } else {
            throw new Error("the params is must be required or type must be Object");
        }
    }
    let { url, leve, near } = AssOpts;

    // 返回路由 page对象
    let backPage;
    // 返回路由 层级数
    let backLeve;
    // leve 存在的情况
    if (leve !== undefined) {
        let leveType = $toClass(leve);
        let backLint = function () {
            if (leve = +leve, Number.isSafeInteger(leve) && leve >= 0) {
                if (leve <= pagesLen - 1) {
                    backLeve = leve;
                    backPage = pages[pagesLen - 1 - backLeve];
                } else {
                    throw new RangeError(`the leve is beyond the range , routes length is ${ pagesLen } , back leve is ${ leve }`);
                    /* 
                        console.warn("the leve is beyond the range");
                        backLeve = pagesLen - 1;
                        backPage = pages[backLeve]; 
                    */
                }
            } else {
                throw new RangeError("the leve is must be a positive integer");
            }
        };
        if (leveType == "String") {
            leve = $trimAll(leve);
            if (leve) {
                if (leve == "max") {
                    backLeve = pagesLen - 1;
                    backPage = pages[backLeve];
                } else {
                    backLint();
                }
            } else {
                console.warn("the leve is null");
            }
        } else if (leveType == "Number") {
            backLint();
        } else {
            throw new TypeError("the leve must be String or Number");
        }
        // url存在的情况
    } else if (url && typeof url == "string" && $trimAll(url)) {
        // 是否就近查找返回路由 只有url单独存在的情况下
        // [b,a,a,b,c]  当前路由为 c
        // 默认就近查找  按照   c->b->a
        // 相反          按照   b->a  
        // 两者产生的效果差异很大

        if (near) {
            pages.reverse();
        }
        let searchPage = pages.find((page, pageIndex) => {
            // 严格查找匹配路由
            if ("/" + page.route === url) {
                backLeve = near ? pageIndex : pagesLen - pageIndex - 1;
                return true;
            }
        });
        if (searchPage) {
            backPage = searchPage;
        } else {
            throw new Error(`the ${url} router is not found`);
        }
    } else {
        throw new ReferenceError("missing key parameters -- leve or url");
    }
    // 查找到 返回路由的情况下  
    if (backPage) {
        // 注意  成功回调函数存在的情况下  最先执行 
        // 最好不要 回调函数与传参同时存在 
        // 如果存在成功回调函数  则把  返回路由对象传给回调函数
        let { options, replace, prevLoad } = AssOpts;
        if (prevLoad && typeof prevLoad == "function") {
            prevLoad(backPage);
        }
        // 判断 options或者replace 存在的情况下  调用 onload
        if (options || replace) {
            let sezOpts = {};
            if (options) {
                //严格类型判断
                let opType = $toClass(options);
                if (opType == 'Object') {
                    sezOpts = options;
                    // 字符类型进行对象序列化
                } else if (opType == 'String') {
                    let trimOpts = $trimAll(options);
                    if (trimOpts) {
                        sezOpts = querystring.parse(trimOpts);
                    }
                } else {
                    throw new TypeError('the params options must be Object or String');
                }
            }
            backPage.onLoad(sezOpts);
            // 如果是返回路由就是当前页面  进行一次刷新
            if (!backLeve) {
                backPage.onReady();
            }
        }

        //如果返回路由存在  调用api返回
        if (backLeve) {
            dd.navigateBack({
                delta: backLeve
            });
        }

    }

}

module.exports = {
    $url
    , $back
};
